// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
	"encoding/json"
	"fmt"
	"os"
	fspath "path"
	"slices"
	"strings"
	"testing"

	"github.com/google/go-cmp/cmp"
)

func TestRenderReferenceCrate(t *testing.T) {
	input, err := testDataPublicCA()
	if err != nil {
		t.Fatal(err)
	}
	outDir := t.TempDir()
	wantUid := "crate.google_cloud_security_publicca_v1"
	id := findIdByUid(t, input, wantUid)
	if err := renderReference(input, id, outDir); err != nil {
		t.Fatal(err)
	}
	contents, err := os.ReadFile(fspath.Join(outDir, fmt.Sprintf("%s.yml", wantUid)))
	if err != nil {
		t.Fatal(err)
	}
	lines := strings.Split(string(contents), "\n")
	idx := slices.IndexFunc(lines, func(a string) bool { return strings.Contains(a, "summary: |") })
	if idx == -1 {
		t.Fatalf("missing `summary: |` line in output YAML %s", contents)
	}
	got := lines[idx+1 : idx+4]
	want := []string{
		"    Google Cloud Client Libraries for Rust - Public Certificate Authority API",
		"    ",
		"    This crate contains traits, types, and functions to interact with Public Certificate Authority API",
	}
	if diff := cmp.Diff(want, got); diff != "" {
		t.Errorf("mismatched summary lines in generated YAML (-want +got):\n%s", diff)
	}
}

func TestRenderReferenceFunction(t *testing.T) {
	input, err := testDataPublicCA()
	if err != nil {
		t.Fatal(err)
	}
	outDir := t.TempDir()
	wantUid := "struct.google_cloud_security_publicca_v1.client.PublicCertificateAuthorityService"
	id := findIdByUid(t, input, wantUid)
	if err := renderReference(input, id, outDir); err != nil {
		t.Fatal(err)
	}
	contents, err := os.ReadFile(fspath.Join(outDir, fmt.Sprintf("%s.yml", wantUid)))
	if err != nil {
		t.Fatal(err)
	}
	lines := strings.Split(string(contents), "\n")
	// Find the section for the `builder()` function
	functionStart := fmt.Sprintf("- uid: %s.builder", wantUid)
	idx := slices.Index(lines, functionStart)
	if idx == -1 {
		t.Fatalf("missing %s in output YAML %s", functionStart, contents)
	}
	lines = lines[idx:]
	idx = slices.Index(lines, "  summary: |")
	want := []string{
		"    ```rust",
		"    fn builder() -> super::builder::public_certificate_authority_service::ClientBuilder",
		"    ```",
		"    ",
		"    Returns a builder for [PublicCertificateAuthorityService].",
	}
	if diff := cmp.Diff(want, lines[idx+1:idx+1+len(want)]); diff != "" {
		t.Errorf("mismatched summary lines in generated YAML (-want +got):\n%s", diff)
	}
}

func TestRenderReferenceFields(t *testing.T) {
	input, err := testDataPublicCA()
	if err != nil {
		t.Fatal(err)
	}
	outDir := t.TempDir()
	wantUid := "struct.google_cloud_security_publicca_v1.model.ExternalAccountKey"
	id := findIdByUid(t, input, wantUid)
	if err := renderReference(input, id, outDir); err != nil {
		t.Fatal(err)
	}
	contents, err := os.ReadFile(fspath.Join(outDir, fmt.Sprintf("%s.yml", wantUid)))
	if err != nil {
		t.Fatal(err)
	}
	lines := strings.Split(string(contents), "\n")
	// Find the section for the `b64_mac_key` field
	functionStart := fmt.Sprintf("- uid: %s.b64_mac_key", wantUid)
	idx := slices.Index(lines, functionStart)
	if idx == -1 {
		t.Fatalf("missing %s in output YAML %s", functionStart, contents)
	}
	lines = lines[idx:]
	idx = slices.Index(lines, "  summary: |")
	want := []string{
		"    Output only. Base64-URL-encoded HS256 key.",
		"    It is generated by the PublicCertificateAuthorityService",
		"    when the ExternalAccountKey is created",
	}
	if diff := cmp.Diff(want, lines[idx+1:idx+1+len(want)]); diff != "" {
		t.Errorf("mismatched summary lines in generated YAML (-want +got):\n%s", diff)
	}
}

func TestRenderReferenceTypeAlias(t *testing.T) {
	input, err := testDataPublicCA()
	if err != nil {
		t.Fatal(err)
	}
	outDir := t.TempDir()
	wantUid := "typealias.google_cloud_security_publicca_v1.builder.public_certificate_authority_service.ClientBuilder"
	id := findIdByUid(t, input, wantUid)
	if err := renderReference(input, id, outDir); err != nil {
		t.Fatal(err)
	}
	contents, err := os.ReadFile(fspath.Join(outDir, fmt.Sprintf("%s.yml", wantUid)))
	if err != nil {
		t.Fatal(err)
	}
	lines := strings.Split(string(contents), "\n")
	// Find the section for the `ClientBuilder` type alias
	functionStart := fmt.Sprintf("- uid: %s", wantUid)
	idx := slices.Index(lines, functionStart)
	if idx == -1 {
		t.Fatalf("missing %s in output YAML %s", functionStart, contents)
	}
	lines = lines[idx:]
	idx = slices.Index(lines, "  summary: |")
	want := []string{
		"    ```rust",
		"    pub type ClientBuilder = gax::client_builder::ClientBuilder<client::Factory, gaxi::options::Credentials>;",
		"    ```",
	}
	if diff := cmp.Diff(want, lines[idx+1:idx+1+len(want)]); diff != "" {
		t.Errorf("mismatched summary lines in generated YAML (-want +got):\n%s", diff)
	}
}

func TestRenderReferenceTypeAliasPrimitive(t *testing.T) {
	input, err := testDataPublicCA()
	if err != nil {
		t.Fatal(err)
	}
	outDir := t.TempDir()
	wantUid := "typealias.google_cloud_security_publicca_v1.model.UInt64Value"
	id := findIdByUid(t, input, wantUid)
	if err := renderReference(input, id, outDir); err != nil {
		t.Fatal(err)
	}
	contents, err := os.ReadFile(fspath.Join(outDir, fmt.Sprintf("%s.yml", wantUid)))
	if err != nil {
		t.Fatal(err)
	}
	lines := strings.Split(string(contents), "\n")
	// Find the section for the `UInt64Value` type alias
	functionStart := fmt.Sprintf("- uid: %s", wantUid)
	idx := slices.Index(lines, functionStart)
	if idx == -1 {
		t.Fatalf("missing %s in output YAML %s", functionStart, contents)
	}
	lines = lines[idx:]
	idx = slices.Index(lines, "  summary: |")
	want := []string{
		"    ```rust",
		"    pub type UInt64Value = u64;",
		"    ```",
	}
	if diff := cmp.Diff(want, lines[idx+1:idx+1+len(want)]); diff != "" {
		t.Errorf("mismatched summary lines in generated YAML (-want +got):\n%s", diff)
	}
}

func findIdByUid(t *testing.T, crate *crate, uid string) string {
	t.Helper()
	for id := range crate.Index {
		if got, err := crate.getDocfxUid(id); err == nil {
			if got == uid {
				return id
			}
		}
	}
	t.Fatalf("cannot find uid %s", uid)
	return ""
}

func testDataPublicCA() (*crate, error) {
	contents, err := os.ReadFile("testdata/google_cloud_security_publicca_v1.json")
	if err != nil {
		return nil, err
	}
	crate := new(crate)
	// Our parser cannot handle certain attributes, we ignore errors for now
	_ = json.Unmarshal(contents, &crate)
	return crate, nil
}
